﻿/*
Bf3Rcon.NET, provides a .NET implementation of BF3's RCON interface.
Copyright (C) 2011 Timi

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

You can contact us at http://bf3rcon.codeplex.com/.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Security.Cryptography;

namespace System.Net.Battlefield3.Server
{
    //this will be a near clone of rconclient, with the main difference being lack of data classes like ServerInformation
    //inheritance is not feasible because rconclient must inherit from rconclientbase, which is for async and sync
    class RconServerClient
        : IDisposable
    {
        internal TcpClient Client;
        internal RconServer Parent;
        internal Socket Socket { get { return Client.Client; } } //assume this will always work
        const int BufferSize = 4096;
        internal List<byte> LeftoverData = new List<byte>(BufferSize);

        internal bool EventsEnabled { get; set; }

        object PasswordLock = new object();
        byte[] SaltBytes;
        internal string GetNewSalt()
        {
            lock (PasswordLock)
            {
                SaltBytes = new byte[16];

                using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
                {
                    rng.GetBytes(SaltBytes);
                }

                return SaltBytes.ToHexString();
            }
        }
        internal bool PasswordMatches(string pass, string hash)
        {
            lock (PasswordLock)
            {
                if (SaltBytes == null || !pass.HasValue()) return false;

                //taken from the GeneratePasswordHash method
                string correctHash;
                using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
                {
                    byte[] passBytes = Encoding.Default.GetBytes(pass);

                    byte[] allBytes = new byte[passBytes.Length + 16];

                    for (int i = 0; i < 16; i++)
                        allBytes[i] = SaltBytes[i]; //copy over salt bytes

                    for (int i = 0; i < passBytes.Length; i++)
                        allBytes[i + 16] = passBytes[i];

                    correctHash = md5.ComputeHash(allBytes).ToHexString();
                }

                return correctHash.CaseInsensitiveEquals(hash);
            }
        }

        internal RconServerClient(RconServer parent, TcpClient client, int id)
        {
            Client = client;
            Parent = parent;
            Id = id;
        }

        #region Properties
        internal bool IsLoggedOn { get; set; }

        internal bool IsConnected
        {
            get
            {
                try
                {
                    return Client != null && Client.Connected;
                }
                catch (NullReferenceException) //only necessary for mono afaik
                {
                    return false;
                }
                
            }
        }

        internal int Id { get; private set; }
        #endregion

        object DisconnectLockObject = new object();
        internal void Disconnect()
        {
            //this will not remove the client from rconserver because the server will enumerate through client for events
            //the server will do the removal
            lock (DisconnectLockObject)
            {
                if (Client != null)
                {
                    Client.Close();
                    Client = null;
                    //no unique resetting is done because clients will not be reused
                }
            }
        }



        #region IDisposable Members
        private bool disposed = false;

        /// <summary>
        /// Disposes the RconClient object.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Disposes the RconClient object.
        /// </summary>
        /// <param name="disposing">Whether or not to release managed resources.</param>
        void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    Disconnect();
                }
            }

            disposed = true;
        }

        #endregion

    }
}
